/*
    Copyright (C) 2003 Janne Jalkanen (Janne.Jalkanen@iki.fi)

    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
 */
package erwiki.plugins.denounce;

import org.apache.log4j.Logger;
import org.apache.oro.text.GlobCompiler;
import org.apache.oro.text.regex.MalformedPatternException;
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.PatternCompiler;
import org.apache.oro.text.regex.PatternMatcher;
import org.apache.oro.text.regex.Perl5Matcher;
import erwiki.api.core.WikiContext;
import erwiki.api.exceptions.PluginException;
import erwiki.util.TextUtil;
import erwiki.api.plugin.WikiPlugin;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Плагин ErWiki. Денонсирует ссылку, удаляя ее из любой поисковой системы.</br>
 * Боты перечислены в файле <i>denounce.properties</i>.
 * <p>
 * Параметры плагина:
 * <ul>
 * <li><b>link</b> - Ссылка на денонсацию, этот параметр обязателен.</li>
 * <li><b>text</b> - Используемый текст, по умолчанию – берется из параметра <i>link</i>.</li>
 * </ul>
 */
public class DenouncePlugin implements WikiPlugin {

	private static final Logger log = Logger.getLogger(DenouncePlugin.class);

	/** Имя параметра для настройки <i>link</i>. Значение <tt>{@value}</tt>. */
	public static final String PARAM_LINK = "link";

	/** Имя параметра для настройки <i>text</i>. Значение <tt>{@value}</tt>. */
	public static final String PARAM_TEXT = "text";

	private static final String PROPERTYFILE = "denounce.properties";
	private static final String PROP_AGENTPATTERN = "denounce.agentpattern.";
	private static final String PROP_HOSTPATTERN = "denounce.hostpattern.";
	private static final String PROP_REFERERPATTERN = "denounce.refererpattern.";

	private static final String PROP_DENOUNCETEXT = "denounce.denouncetext";

	private static ArrayList<Pattern> c_refererPatterns = new ArrayList<>();
	private static ArrayList<Pattern> c_agentPatterns = new ArrayList<>();
	private static ArrayList<Pattern> c_hostPatterns = new ArrayList<>();

	private static String c_denounceText = "";

	/**
	 * Подготавливает различные шаблоны для последующего использования.</br>
	 * Компиляция (вероятно) дорога, поэтому шаблоны вычисляются статически во время загрузки класса.
	 */
	static {
		try {
			PatternCompiler compiler = new GlobCompiler();
			ClassLoader loader = DenouncePlugin.class.getClassLoader();

			String packageName = DenouncePlugin.class.getPackageName().replace('.', '/');
			InputStream in = loader.getResourceAsStream(packageName + '/' + PROPERTYFILE);

			if (in == null) {
				throw new IOException("No property file found! (Check the installation, it should be there.)");
			}

			Properties props = new Properties();
			props.load(in);

			c_denounceText = props.getProperty(PROP_DENOUNCETEXT, c_denounceText);

			for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();) {
				String name = (String) e.nextElement();

				try {
					if (name.startsWith(PROP_REFERERPATTERN)) {
						c_refererPatterns.add(compiler.compile(props.getProperty(name)));
					} else if (name.startsWith(PROP_AGENTPATTERN)) {
						c_agentPatterns.add(compiler.compile(props.getProperty(name)));
					} else if (name.startsWith(PROP_HOSTPATTERN)) {
						c_hostPatterns.add(compiler.compile(props.getProperty(name)));
					}
				} catch (MalformedPatternException ex) {
					log.error("Malformed URL pattern in " + PROPERTYFILE + ": " + props.getProperty(name), ex);
				}
			}

			log.debug("Added " + c_refererPatterns.size() + c_agentPatterns.size() + c_hostPatterns.size()
					+ " crawlers to denounce list.");
		} catch (IOException e) {
			log.error("Unable to load URL patterns from " + PROPERTYFILE, e);
		} catch (Exception e) {
			log.error("Unable to initialize Denounce plugin", e);
		}
	}

	/** {@inheritDoc} */
	@Override
	public String execute(WikiContext context, Map<String, String> params) throws PluginException {
		String link = params.get(PARAM_LINK);
		String text = params.get(PARAM_TEXT);
		boolean linkAllowed = true;

		if (link == null) {
			throw new PluginException("Denounce: No parameter " + PARAM_LINK + " defined!");
		}

		HttpServletRequest request = context.getHttpRequest();

		if (request != null) {
			linkAllowed = !matchHeaders(request);
		}

		if (text == null) {
			text = link;
		}

		if (linkAllowed) {
			// FIXME: Should really call TranslatorReader
			return "<a href=\"" + link + "\">" + TextUtil.replaceEntities(text) + "</a>";
		}

		return c_denounceText;
	}

	/**
	 * Возвращает <code>true</code>, если заданный 'path' найден среди ссылающихся источников.
	 */
	private boolean matchPattern(List<Pattern> list, String path) {
		PatternMatcher matcher = new Perl5Matcher();
		for (Pattern pattern : list) {
			if (matcher.matches(path, pattern)) {
				return true;
			}
		}

		return false;
	}

	// FIXME: Should really return immediately when a match is found.

	private boolean matchHeaders(HttpServletRequest request) {
		//
		// User Agent
		//

		String userAgent = request.getHeader("User-Agent");

		if (userAgent != null && matchPattern(c_agentPatterns, userAgent)) {
			log.debug("Matched user agent " + userAgent + " for denounce.");
			return true;
		}

		//
		// Referrer header
		//

		String refererPath = request.getHeader("Referer");

		if (refererPath != null && matchPattern(c_refererPatterns, refererPath)) {
			log.debug("Matched referer " + refererPath + " for denounce.");
			return true;
		}

		//
		// Host
		//

		String host = request.getRemoteHost();

		if (host != null && matchPattern(c_hostPatterns, host)) {
			log.debug("Matched host " + host + " for denounce.");
			return true;
		}

		return false;
	}

}
